#pragma once

#include <math.h>
#include <sys/time.h>
#include <time.h>
#include <cstdio>
#include <chrono>
#include <thread>

namespace TimeUtil {

// from android samples
/* return current time in milliseconds */
inline double now_ms_double() {
  struct timespec now;
  clock_gettime(CLOCK_MONOTONIC, &now);
  return 1000.0 * now.tv_sec + (double)now.tv_nsec / 1e6;
}

inline uint64_t now_ns() {
  struct timespec t;
  clock_gettime(CLOCK_REALTIME, &t);
  return t.tv_nsec + ((uint64_t)t.tv_sec * 1000 * 1000 * 1000);
}

inline uint64_t now_ms() {
  struct timespec t;
  clock_gettime(CLOCK_REALTIME, &t);
  return t.tv_nsec / 1000000 + ((uint64_t)t.tv_sec * 1000);
}

inline uint64_t now_us() {
  struct timespec t;
  clock_gettime(CLOCK_REALTIME, &t);
  return t.tv_nsec / 1000 + ((uint64_t)t.tv_sec * 1000000);
}

template <typename t = std::chrono::steady_clock>
inline unsigned long long now_ms() {
  using namespace std::chrono;
  // Get current time with precision of milliseconds
  auto now = time_point_cast<std::chrono::milliseconds>(t::now());
  // Convert time_point to signed integral type
  return now.time_since_epoch().count();
}

template <typename t = std::chrono::steady_clock>
inline unsigned long long now_us() {
  using namespace std::chrono;
  // Get current time with precision of microseconds
  auto now = time_point_cast<std::chrono::microseconds>(t::now());
  // Convert time_point to signed integral type
  return now.time_since_epoch().count();
}

inline std::chrono::time_point<std::chrono::system_clock>
ms_to_time(unsigned long long ms) {
  using namespace std::chrono;
  return time_point<std::chrono::system_clock>(std::chrono::milliseconds(ms));
}

inline void sleep(long milliseconds) {
  std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
}

inline bool printTime(char *buf, int maxLen, unsigned long long mill) {
  using namespace std::chrono;
  auto timepoint = ms_to_time(mill);
  std::chrono::milliseconds ms = std::chrono::milliseconds(mill);
  // std::chrono::seconds s = duration_cast<std::chrono::seconds>(ms);
  std::time_t t = system_clock::to_time_t(timepoint);
  std::size_t fractional_seconds = ms.count() % 1000;
  struct tm *mtm = localtime(&t);
  if (mtm == nullptr) {
    buf[0] = '\0';
    return false;
  }
  int res = snprintf(buf, maxLen, "%d-%d %d:%d:%d.%3zu", mtm->tm_mon + 1,
                     mtm->tm_mday, mtm->tm_hour, mtm->tm_min, mtm->tm_sec,
                     fractional_seconds);
  return res > 0 && res < maxLen;
}

inline bool printTimestamp(char *buf, int maxLen) {
#if 1
  using namespace std::chrono;
  system_clock::time_point now = system_clock::now();
  std::chrono::milliseconds ms =
      duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
  // std::chrono::seconds s = duration_cast<std::chrono::seconds>(ms);
  std::time_t t = system_clock::to_time_t(now);
  std::size_t fractional_seconds = ms.count() % 1000;
  struct tm *mtm = localtime(&t);
  if (mtm == nullptr) {
    buf[0] = '\0';
    return false;
  }
#else
  long fractional_seconds; // Milliseconds
  time_t s;                // Seconds
  struct timespec spec;
  clock_gettime(CLOCK_REALTIME, &spec);
  // get millseconds
  s = spec.tv_sec;
  fractional_seconds =
      round(spec.tv_nsec / 1.0e6); // Convert nanoseconds to milliseconds
  if (fractional_seconds > 999) {
    s++;
    fractional_seconds = 0;
  }
  // get date
  struct tm *mtm = localtime(&s);
#endif
  int res = snprintf(buf, maxLen, "%d-%d %d:%d:%d.%3zu", mtm->tm_mon + 1,
                     mtm->tm_mday, mtm->tm_hour, mtm->tm_min, mtm->tm_sec,
                     fractional_seconds);
  return res > 0 && res < maxLen;
}

inline bool printDate(char *date, const int maxLen) {
  using namespace std::chrono;
  std::time_t curtime = system_clock::to_time_t(system_clock::now());
  strftime(date, maxLen, "%Y-%m-%d", localtime(&curtime));
  return true;
}

struct FpsCalc {
  uint64_t total_start{};
  uint64_t total_count{};

  uint64_t last_start{};
  uint64_t last_count{};

  float avg_fps{};
  float ins_fps{};

  void clear() {
    total_start = 0;
    total_count = 0;
    last_start = 0;
    last_count = 0;

    avg_fps = 0;
    ins_fps = 0;
  }
};

int fps_calc_inc(FpsCalc &ctx);

} // namespace TimeUtil
